package com.inspur.cmp;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.ws.rs.core.Response;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.Authenticator;
import org.keycloak.common.util.RandomString;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.theme.Theme;


public class EmailAuthenticatorDemo implements Authenticator {

	private static final String TPL_CODE = "login-email-demo.ftl";
	private static final int CODE_REFRESH_LIMIT = 60000;

	@Override
	public void authenticate(AuthenticationFlowContext context) {
		AuthenticatorConfigModel config = context.getAuthenticatorConfig();
		if(null == config) {
			config = this.getDefaultConfig();
		}
		KeycloakSession session = context.getSession();
		UserModel user = context.getUser();

		String email = user.getEmail();
		String[] emailParts = email.split("@");
		if (emailParts[0].length() > 2) {
			emailParts[0] = emailParts[0].substring(0, 2) + "****";
		}

		int length = Integer.parseInt(config.getConfig().get("length"));
		int ttl = Integer.parseInt(config.getConfig().get("ttl"));

		String code = RandomString.randomCode(length);
		AuthenticationSessionModel authSession = context.getAuthenticationSession();

		String emailSentTime = authSession.getAuthNote("emailSentTime");
		if (emailSentTime == null || emailSentTime != null && System.currentTimeMillis() - CODE_REFRESH_LIMIT > Long.valueOf(emailSentTime)) {
			authSession.setAuthNote("code", code);
			authSession.setAuthNote("ttl", Long.toString(System.currentTimeMillis() + (ttl * 1000)));

			try {
				Theme theme = session.theme().getTheme(Theme.Type.LOGIN);
				Locale locale = session.getContext().resolveLocale(user);
				String emailAuthText = theme.getMessages(locale).getProperty("emailAuthText");
				String emailAuthSubject = theme.getMessages(locale).getProperty("emailAuthTitle");
				String emailText = String.format(emailAuthText, code, Math.floorDiv(ttl, 60));

				EmailServiceFactory.get(config.getConfig()).send(emailText, emailAuthSubject, context);

				emailSentTime = Long.toString(System.currentTimeMillis());
				authSession.setAuthNote("emailSentTime", emailSentTime);
			} catch (Exception e) {
				context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR,
						context.form().setError("emailAuthSmsNotSent", e.getMessage())
								.createErrorPage(Response.Status.INTERNAL_SERVER_ERROR));
			}
		}

		Long counting = (CODE_REFRESH_LIMIT - (System.currentTimeMillis() - Long.valueOf(emailSentTime))) / 1000;

		LoginFormsProvider forms = context.form();
		forms.setAttribute("realm", context.getRealm());
		forms.setAttribute("counting", String.valueOf(counting));
		forms.setAttribute("email", emailParts[0]+"@"+emailParts[1]);
		Response challenge = forms.createForm(TPL_CODE);
		context.challenge(challenge);
	}

	@Override
	public void action(AuthenticationFlowContext context) {
		String enteredCode = context.getHttpRequest().getDecodedFormParameters().getFirst("code").trim();

		AuthenticationSessionModel authSession = context.getAuthenticationSession();
		String code = authSession.getAuthNote("code");
		String ttl = authSession.getAuthNote("ttl");

		if (code == null || ttl == null) {
			context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR,
				context.form().createErrorPage(Response.Status.INTERNAL_SERVER_ERROR));
			return;
		}

		boolean isValid = enteredCode.equals(code);
		if (isValid) {
			if (Long.parseLong(ttl) < System.currentTimeMillis()) {
				// expired
				context.failureChallenge(AuthenticationFlowError.EXPIRED_CODE,
					context.form().setError("emailAuthCodeExpired").createErrorPage(Response.Status.BAD_REQUEST));
			} else {
				// valid
				context.success();
			}
		} else {
			// invalid
			AuthenticationExecutionModel execution = context.getExecution();
			if (execution.isRequired()) {
				UserModel user = context.getUser();
				String markedEmail = user.getEmail().replaceAll("(\\w?)(\\w+)(\\w)(@\\w+\\.[a-z]+(\\.[a-z]+)?)", "$1****$3$4");
				String emailSentTime = authSession.getAuthNote("emailSentTime");
				Long counting = (CODE_REFRESH_LIMIT - (System.currentTimeMillis() - Long.valueOf(emailSentTime))) / 1000;

				LoginFormsProvider forms = context.form();
				forms.setAttribute("realm", context.getRealm());
				forms.setAttribute("counting", String.valueOf(counting));
				forms.setAttribute("email", markedEmail);
				forms.setError("emailAuthCodeInvalid");
				Response challenge = forms.createForm(TPL_CODE);
				context.failure(AuthenticationFlowError.INVALID_CREDENTIALS);
				context.challenge(challenge);
			} else if (execution.isConditional() || execution.isAlternative()) {
				context.attempted();
			}
		}
	}

	@Override
	public boolean requiresUser() {
		return false;
	}

	@Override
	public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
		return user.getEmail() != null;
	}

	@Override
	public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
	}

	@Override
	public void close() {
	}

	private AuthenticatorConfigModel getDefaultConfig(){
		AuthenticatorConfigModel config = new AuthenticatorConfigModel();
		Map<String, String> configMap = new HashMap<String, String>();
		configMap.put("length", "6");
		configMap.put("ttl", "300");
		configMap.put("senderId", "InCloudOS");
		configMap.put("simulation", "false");

		config.setConfig(configMap);
		return config;
	}
}
